
#ifndef __LIBERTY_LIBRARY_SYNCEDCACHE_H__
#define __LIBERTY_LIBRARY_SYNCEDCACHE_H__

#include <Liberty3D/Liberty3D.h>
#include <Liberty3D/Library/Task.h>
#include <Liberty3D/Library/TaskScheduler.h>

#include <boost/thread/shared_mutex.hpp>
#include <boost/thread/locks.hpp>
#include <list>
#include <unordered_map>

namespace Liberty { namespace Library {

    template <typename Key, typename Value>
    class SyncedCacheBasicConfig {
    public:
        typedef Key KeyParam;

        typedef typename std::shared_ptr<Value> SharedValue;
        typedef typename std::weak_ptr<Value>   WeakValue;

        typedef typename boost::shared_mutex                  Mutex;
        typedef typename boost::shared_lock<Mutex>            SharedLock;
        typedef typename boost::unique_lock<Mutex>            UniqueLock;
        typedef typename boost::upgrade_lock<Mutex>           UpgradableLock;
        typedef typename boost::upgrade_to_unique_lock<Mutex> UpgradedLock;

        typedef Key       MapEntryKey;
        typedef WeakValue MapEntryValue;
    };

    template <typename Provider>
    class SyncedCache {
    public:
        typedef typename std::unordered_map<typename Provider::MapEntryKey, typename Provider::MapEntryValue> Map;

    protected:
        Provider                 _provider;
        Map                      _map;
        typename Provider::Mutex _mutex;

    public:
        SyncedCache(const Provider& provider) : _provider(provider) {
        }

        typename Provider::SharedValue obtain(const typename Provider::KeyParam& key) {
            typename Provider::SharedValue value, newValue;

            {
                typename Provider::UpgradableLock guard      (_mutex);
                typename Provider::MapEntryValue& mappedValue(find(key, &guard));

                value = mappedValue.lock();

                if (value) {
                    return value;
                }
            }

            bool created;

            newValue = _provider.create(key, created);

            {
                typename Provider::UniqueLock     guard      (_mutex);
                typename Provider::MapEntryValue& mappedValue(find(key, nullptr));

                value = mappedValue.lock();

                if (value) {
                    return value;
                }

                mappedValue = value = newValue;

                if (created) {
                    _provider.dirty(value);
                }
            }

            return value;
        }

        void clear(const typename Provider::KeyParam& key) {
            typename Provider::UpgradableLock guard(_mutex);

            typename Map::iterator it(_map.find(key));

            if (it != _map.end()) {
                typename Provider::UpgradedLock guardEx(guard);

                _map.erase(it);
            }
        }

        inline Provider& provider() { return _provider; }

    protected:
        typename Provider::MapEntryValue& find(const typename Provider::KeyParam& key, typename Provider::UpgradableLock* guard) {
            typename Map::iterator it(_map.find(key));

            if (it == _map.end()) {
                std::unique_ptr<typename Provider::UpgradedLock> exclusiveGuard;

                if (guard) {
                    exclusiveGuard = std::unique_ptr<typename Provider::UpgradedLock>(new typename Provider::UpgradedLock(*guard));
                }

                _map.insert(std::pair<typename Provider::MapEntryKey, typename Provider::MapEntryValue>(key, typename Provider::MapEntryValue()));
                it = _map.find(key);
            }

            return (*it).second;
        }
    };
}}

#endif
